use console::{Key, Term};
use lazy_static::lazy_static;
use rand::{thread_rng, Rng};

const X: usize = 4;
const Y: usize = 4;

type List = [i32; X];
type Map = [List; Y];

lazy_static! {
    static ref TERM: Term = Term::stdout();
}

enum Dir {
    UP,
    DOWN,
    LEFT,
    RIGHT,
    UNKNOWN,
}

#[derive(Debug)]
struct Game {
    data: Map,
    w: usize,
    h: usize,
}

impl Game {
    fn new() -> Game {
        Game {
            data: [[0; X]; Y],
            w: X,
            h: Y,
        }
    }

    fn init(&mut self) {
        TERM.set_title("GAME 2048");
        let _ = TERM.hide_cursor();
        self.gen();
        self.gen();
    }

    fn gen(&mut self) -> bool {
        let mut buf = Vec::new();
        for y in 0..self.h {
            for x in 0..self.w {
                if self.data[y][x] == 0 {
                    buf.push((x, y));
                }
            }
        }

        if buf.len() == 0 {
            return false;
        }

        let mut rng = thread_rng();
        let i = rng.gen_range(0..buf.len());
        let (x, y) = buf[i];
        self.data[y][x] = 2;

        true
    }

    fn mv(&mut self, dir: Dir) -> bool {
        fn _mv_line(data: &mut Vec<i32>) -> Vec<i32> {
            let len = data.len();
            if len < 2 {
                return data.clone();
            }

            let mut tmp = Vec::new();
            let mut i = 0;
            while i < len {
                if data.get(i) == data.get(i + 1) {
                    tmp.push(2 * data[i]);
                    i += 2;
                } else {
                    tmp.push(data[i]);
                    i += 1;
                }
            }

            tmp
        }

        fn _mv_up(data: &mut Map) {
            for x in 0..X {
                let mut tmp = Vec::new();
                for y in 0..Y {
                    if data[y][x] != 0 {
                        tmp.push(data[y][x]);
                    }
                    data[y][x] = 0;
                }

                let tmp = _mv_line(&mut tmp);
                for i in 0..tmp.len() {
                    data[i][x] = tmp[i];
                }
            }
        }

        fn _mv_down(data: &mut Map) {
            for x in 0..X {
                let mut tmp = Vec::new();
                for y in 0..Y {
                    if data[Y - 1 - y][x] != 0 {
                        tmp.push(data[Y - 1 - y][x]);
                    }
                    data[Y - 1 - y][x] = 0;
                }

                let tmp = _mv_line(&mut tmp);
                for i in 0..tmp.len() {
                    data[Y - 1 - i][x] = tmp[i];
                }
            }
        }

        fn _mv_left(data: &mut Map) {
            for y in 0..Y {
                let mut tmp = Vec::new();
                for x in 0..X {
                    if data[y][x] != 0 {
                        tmp.push(data[y][x]);
                    }
                    data[y][x] = 0;
                }

                let tmp = _mv_line(&mut tmp);
                for i in 0..tmp.len() {
                    data[y][i] = tmp[i];
                }
            }
        }

        fn _mv_right(data: &mut Map) {
            for y in 0..Y {
                let mut tmp = Vec::new();
                for x in 0..X {
                    if data[y][X - 1 - x] != 0 {
                        tmp.push(data[y][X - 1 - x]);
                    }
                    data[y][X - 1 - x] = 0;
                }

                let tmp = _mv_line(&mut tmp);
                for i in 0..tmp.len() {
                    data[y][X - 1 - i] = tmp[i];
                }
            }
        }

        let origin = self.data.clone();
        match dir {
            Dir::UP => _mv_up(&mut self.data),
            Dir::DOWN => _mv_down(&mut self.data),
            Dir::LEFT => _mv_left(&mut self.data),
            Dir::RIGHT => _mv_right(&mut self.data),
            _ => (),
        }
        return origin != self.data;
    }

    fn get_score(&self) -> i32 {
        let mut score = 0;
        for y in 0..self.h {
            for x in 0..self.w {
                score += self.data[y][x];
            }
        }
        score
    }

    fn draw(&self) {
        fn _draw_line_head(w: usize) {
            let _ = TERM.write_str("+");
            for _ in 0..w {
                let _ = TERM.write_str(format!("{:-^5}+", "").as_ref());
            }
            let _ = TERM.write_str("\n");
        }

        fn _draw_line_data(data: &List) {
            let _ = TERM.write_str("|");
            for x in 0..data.len() {
                if data[x] == 0 {
                    let _ = TERM.write_str(format!("{: ^5}|", "").as_ref());
                } else {
                    let _ = TERM.write_str(format!("{: ^5}|", data[x]).as_ref());
                }
            }
            let _ = TERM.write_str("\n");
        }

        let _ = TERM.clear_screen();
        let _ = TERM.write_str(format!("Score: {}\n", self.get_score()).as_ref());
        _draw_line_head(self.w);
        for y in 0..self.h {
            _draw_line_data(&self.data[y]);
            _draw_line_head(self.w);
        }
    }
}

fn get_dir(key: &Key) -> Dir {
    let dir = match key {
        Key::ArrowUp => Dir::UP,
        Key::ArrowDown => Dir::DOWN,
        Key::ArrowLeft => Dir::LEFT,
        Key::ArrowRight => Dir::RIGHT,
        _ => Dir::UNKNOWN,
    };
    dir
}

fn main() {
    let mut game: Game = Game::new();

    game.init();
    game.draw();

    loop {
        let key = TERM.read_key().unwrap_or(Key::Unknown);
        let dir = get_dir(&key);
        if !game.mv(dir) {
            continue;
        }
        if !game.gen() {
            println!("GAME OVER");
            break;
        }
        game.draw();
    }
}
